package cn.kai.ymcc.service.impl;

import cn.kai.ymcc.domain.Course;
import cn.kai.ymcc.domain.CourseMarket;
import cn.kai.ymcc.domain.KillActivity;
import cn.kai.ymcc.domain.KillCourse;
import cn.kai.ymcc.dto.KillParamDto;
import cn.kai.ymcc.dto.KillPreOrderParamsDto;
import cn.kai.ymcc.mapper.KillCourseMapper;
import cn.kai.ymcc.service.IKillActivityService;
import cn.kai.ymcc.service.IKillCourseService;
import cn.kai.ymcc.util.AssertUtil;
import cn.kai.ymcc.util.CodeGenerateUtils;
import cn.kai.ymcc.vo.CourseItemVo;
import cn.kai.ymcc.vo.CourserOrderVo;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author kai.tang
 * @since 2022-08-05
 */
@Service
@Slf4j
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private IKillActivityService killActivityService;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void save(KillCourse killCourse) {
        KillCourse killCourseTmp = selectByIdAndCourseID(killCourse.getActivityId(), killCourse.getCourseId());
        AssertUtil.isNull(killCourseTmp,"秒杀课程已经存在！");

        //同一个课程不能加入到同一个活动下面
        KillActivity killActivity = killActivityService.selectById(killCourse.getActivityId());
        killCourse.setStartTime(killActivity.getStartTime());
        killCourse.setEndTime(killActivity.getEndTime());
        killCourse.setTimeStr(killActivity.getTimeStr());
        killCourse.setCreateTime(new Date());
        insert(killCourse);
    }

    private KillCourse selectByIdAndCourseID(Long activityId, Long courseId) {
        Wrapper<KillCourse> query = new EntityWrapper<>();
        query.eq("course_id",courseId);
        query.eq("activity_id",activityId);
        return selectOne(query);
    }

    /**
     * 访问量很高，能不查询数据库就不查询数据库
     *
     *   只准查询redis
     * @return
     */
    @Override
    public List<KillCourse> onlineAll() {
        List<KillCourse> allKillCourse = new ArrayList<>();
        //我们可以拿指定前缀的某些key
        Set<Object> keys = redisTemplate.keys("activity:*");
        keys.forEach(bigKey->{
            List values = redisTemplate.opsForHash().values(bigKey);//拿到大key对应的所有小key的值
            allKillCourse.addAll(values);
        });
        return allKillCourse;
        /*List<KillCourse> killCourseList = values.stream().map(obj -> {
            KillCourse killCourse = new KillCourse();
            BeanUtils.copyProperties(obj,killCourse);
            return killCourse;
        }).collect(Collectors.toList());*/
    }

    @Override
    public KillCourse onlineOne(Long activityId, Long killId) {
        AssertUtil.isNotNull(activityId,"请求非法");
        AssertUtil.isNotNull(killId,"请求非法");
        return (KillCourse) redisTemplate.opsForHash().get("activity:" + activityId, killId.toString());
    }

    /**
     * 1.查询出秒杀课程
     * === 是否在秒杀中  ===
     *   登录之后才能秒杀
     *   一个人能不能重复秒杀同一个课程
     *   黑白名单
     *
     * 2.使用信号量扣减库存
     * 3.扣减不成功，给客人一个提示信息，秒杀结束---稍后重试！
     * 4.如果扣减成功--生成预创订单-保存到redis-返回订单号
     * @param dto
     * @return
     */
    @Override
    public String kill(KillParamDto dto) {
        Long startTime = System.currentTimeMillis();
        Long activityId = dto.getActivityId();
        Long killCourseId = dto.getKillCourseId();
        //* 1.查询出秒杀课程
        KillCourse killCourse = onlineOne(activityId, killCourseId);
        //* === 是否在秒杀中  ==
        AssertUtil.isTrue(killCourse.isKilling(),"非法秒杀！！");
        //     *   登录之后才能秒杀
        Long loginId = 3L;
        //     *   一个人能不能重复秒杀同一个课程  ?? @TODO
        String repeatKey = activityId +":"+killCourseId +":"+loginId;
        Object repeatOrder = redisTemplate.opsForValue().get(repeatKey);
        AssertUtil.isNull(repeatOrder,"请勿重复秒杀！！");

        //* 2.使用信号量扣减库存
        String key = activityId +":"+killCourseId;
        // 一个活动下的一个课程，就是一个信号量
        RSemaphore semaphore = redissonClient.getSemaphore(key);//获取唯一的信号量对象
        Integer killCount = 1;//默认秒杀一个，如果是秒杀多个，前端传递过来
        boolean tryAcquire = semaphore.tryAcquire(killCount);
        //* 3.扣减不成功，给客人一个提示信息，秒杀结束---稍后重试！
        AssertUtil.isTrue(tryAcquire,"大兄弟，练练手速再来！！！");
        //* 4.如果扣减成功--生成预创订单-保存到redis-返回订单号
        String orderNo = CodeGenerateUtils.generateOrderSn(loginId);
        KillPreOrderParamsDto killPreOrderParamsDto = new KillPreOrderParamsDto(
                orderNo,
                killCount,
                killCourse.getKillPrice(),
                loginId,
                killCourse.getCourseId(),//课程ID
                killCourse.getCourseName(),
                killCourse.getCoursePic()
        );
        redisTemplate.opsForValue().set(orderNo,killPreOrderParamsDto);

        //存储秒杀成功信息，防止重复秒杀
        redisTemplate.opsForValue().set(repeatKey,orderNo);
        log.info("秒杀执行时间：{}", System.currentTimeMillis()-startTime);
        return orderNo;
    }

    @Override
    public CourserOrderVo oneByOrder(String orderNo) {
        //1.从redis中获取预创订单
        KillPreOrderParamsDto preOrder = (KillPreOrderParamsDto) redisTemplate.opsForValue().get(orderNo);

        //2.根据预创订单封装我们想要的Vo结构
        AssertUtil.isNotNull(preOrder,"请求非法！");
        //2.根据每个课程Id查询课程 + 销售表
        List<CourseItemVo> courseInfos = new ArrayList<>();
        Course course = new Course();
        course.setId(preOrder.getCourseId());
        course.setName(preOrder.getCourseName());
        course.setPic(preOrder.getCoursePic());

        CourseMarket courseMarket = new CourseMarket();
        courseMarket.setPrice(preOrder.getKillAmount());

        CourseItemVo courseItemVo = new CourseItemVo(course, courseMarket);
        courseInfos.add(courseItemVo);
        //4.将封装好的List<CourseItemVo> + 总金额，封装成CourserOrderVo
        return new CourserOrderVo(courseInfos,preOrder.getKillAmount());
    }
}
